package codec

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"gitee.com/gcom/gbox/errors"
	"gitee.com/gcom/ths_socket/socks5"
	"github.com/golang/snappy"
	"strconv"
)

type EncType uint8

const (
	EncDes EncType = iota
	EncBigInt
	EncAes
	EncPlain
)

// 服务端数据包
// 参考: com.hexin.plat.android.net.Socket$a(ReceiveThread)
// 以及：eci, eay
type ServerPack struct {
	HeadLength  int32
	PackageId   int32
	HeadType    int32
	FrameId     int32
	PageId      int32
	DataLength  int32 //DataLength和TextLength必有一个为0，DataLength优先
	TextLength  int32
	SessionType int32 // 0: HangQing 1: CBAS
	Body        []byte
	Compressed  bool // Data/Text区域是否有压缩后长度字段
	Encrypt     EncType
	TextData    string // 文本数据
	BinData     []byte // 二进制数据
	OriginData  []byte // 原始包数据，用于转发
	Model       string // 返回数据模型类
}

var serPackAnchor = [...]byte{0xFD, 0xFD, 0xFD, 0xFD}

func ReadServerPack(con socks5.BufferedConn, sessionType int32) (*ServerPack, error) {
	var head = make([]byte, 13)
	_, err := con.ReadFull(head[:])
	if err != nil {
		return nil, err
	}
	if !bytes.Equal(head[:4], serPackAnchor[:]) {
		return nil, errors.Newf("无效的数据包, %s", hex.EncodeToString(head[:]))
	}
	var ls = string(head[4:12])
	l, err := strconv.ParseInt(ls, 16, 32)
	if err != nil {
		return nil, errors.Wrapf(err, "无效的数据包长度: %s", ls)
	}

	if l == 0 { // 心跳包
		return nil, nil
	}

	var data = make([]byte, 13+int(l))
	body := data[13:]
	if _, err = con.ReadFull(body); err != nil {
		return nil, errors.Wrap(err, "读取body失败")
	}

	sp := ServerPack{}
	copy(data[:13], head[:])
	sp.OriginData = data
	sp.HeadLength = int32(binary.LittleEndian.Uint16(body[0:2]))
	sp.PackageId = int32(binary.LittleEndian.Uint16(data[2:6]))
	sp.HeadType = int32(binary.BigEndian.Uint32(body[6:10]))
	sp.PageId = int32(binary.LittleEndian.Uint16(data[10:12]))
	sp.DataLength = int32(binary.LittleEndian.Uint32(body[12:16]))
	sp.FrameId = int32(binary.LittleEndian.Uint32(body[16:20]))
	sp.TextLength = int32(binary.LittleEndian.Uint32(body[20:24]))
	if sp.PageId == 8888 {
		sp.HeadType |= 10
	}
	if l > 25 {
		sp.Body = body[24:] // 服务端数据包头为24字节

		var en = uint32(sp.HeadType) & (uint32(0xF0) << 24)
		switch en {
		case uint32(1) << 28:
			sp.Encrypt = EncDes
			break
		case uint32(1) << 29:
			sp.Encrypt = EncBigInt
			break
		case uint32(7) << 28:
			sp.Encrypt = EncAes
			break
		default:
			sp.Encrypt = EncPlain
		}

		var originData []byte
		if (sp.HeadType & 0xF000) == 0x1000 {
			sp.Compressed = true
			offset := int32(0)
			end := len(sp.Body)
			mode := uint32(sp.HeadType) & 0xF0000000
			if mode == 0x10000000 || mode == 0x20000000 || mode == 0x70000000 {
				offset = 4
				end = 4 + int(binary.LittleEndian.Uint32(body[24:28]))
			}
			originData, err = snappy.Decode(nil, sp.Body[offset:end])
			if err != nil {
				return nil, errors.New("解压数据失败")
			}
			textLen, e := snappy.DecodedLen(sp.Body[offset : sp.TextLength+offset])
			if e != nil {
				return nil, errors.New("解压数据失败")
			}
			sp.TextLength = int32(textLen)
			sp.DataLength = int32(len(originData))
			sp.BinData = originData
			sp.TextData = string(originData[:sp.TextLength])
		} else {
			originData = sp.Body[4:]
		}
		sp.BinData = originData

		if sp.PackageId == -3 {
			sp.Model = "ebd-3"
		} else {
			if len(sp.Body) < 6 {
				sp.Model = "null"
				return &sp, nil
			}
			model := sp.HeadType & 0x0F
			switch model {
			case 0, 1, 3, 13:
				switch string(originData[0:2]) {
				case "tb":
					sp.Model = "StuffTableStruct"
					break
				case "cv":
					sp.Model = "StuffCurveStruct"
					break
				case "8,":
					sp.Model = "ebd3_8"
					break
				default:
					sp.Model = "null"
				}
				break
			case 4:
				sp.Model = "ebd1"
				break
			case 6:
				sp.Model = "ebd3"
				sp.PageId = 8890
				break
			case 7:
				sp.Model = "ebd4"
				sp.PageId = 8889
				break
			case 9:
				sp.Model = "ebd4"
				break
			case 11:
				sp.Model = "ebd5"
				break
			case 15:
				sp.Model = "ebd2"
				break
			default:
				sp.Model = "base"
				break
			}
		}
	}

	return &sp, nil
}

func Compressed(frameId, headType int32) bool {
	return (frameId == 4401 || frameId == 2199 || frameId == 2200) && (0xF000&headType) == 0x1FFF
}
